import java.io.*;
import java.util.*;
import gnu.io.*;

/**
 * Creates a serial connection with the com port that is given.  Stores the data
 * in a queue that can be recovered from other threads.  When retrieving information,
 * it returns a generic object array that has two elements, a time stamp and a
 * string containing the output from the com port.
 * 
 * @author Scott Norman <snorman1@umbc.edu>
 * @version 1.0
 * 
 */
public class Kpodconnection implements Runnable, SerialPortEventListener
{
	private OutputStream out;
	private InputStream in;
	private CommPortIdentifier portId;
	private SerialPort serialPort;
	private Thread serialThread;
	private boolean open = false;
	private String portName = "COM1";
	private int baudRate = 57600;
	private int dBits = SerialPort.DATABITS_8;
	private int sBits = SerialPort.STOPBITS_1;
	private int parity = SerialPort.PARITY_NONE;
	private MessageQueue messageQueue = new MessageQueue();
	private boolean debug = false;
	
	/*public static void main(String[] args) 
	{
		Kpodconnection k = new Kpodconnection("COM8");
		k.setDebug( true );
		k.openConnection();
		try
		{
			k.sendString("1");
		}
		catch( IOException e ) { e.printStackTrace(); }
	}*/

	/**
	 * Default Class constructor.
	 */
	public Kpodconnection( )
	{
	
	}
	
	/**
	 * Class constructor.
	 * @param portName 	the name of the port to open
	 */
	public Kpodconnection( String portName )
	{
		this.portName = portName;
	}
	
	/**
	 * Opens the connection to the com port and start the listener event.
	 * 
	 * @see		#closeConnection()
	 * @since 	1.0
	 */
	public void openConnection()
	{
		try
		{
			portId = CommPortIdentifier.getPortIdentifier( portName );
		}
		catch( NoSuchPortException e ) {}
		
		try
		{
			serialPort = (SerialPort)portId.open( this.getClass().getName(), 2000 );
		}
		catch( PortInUseException e ) {}
		
		try
		{
			serialPort.setSerialPortParams(baudRate, dBits, sBits, parity);
			out = serialPort.getOutputStream();
			in = serialPort.getInputStream();
			out.flush();
		}
		catch( Exception e )
		{
			serialPort.close();
		}
		
		try
		{
			serialPort.addEventListener(this);
		}
		catch( TooManyListenersException e )
		{
			serialPort.close();
		}
		
		serialPort.notifyOnDataAvailable(true);
		open = true;
		
		serialThread = new Thread(this);
		serialThread.start();
	}
	
	/**
	 * Closes the serial connection.
	 * 
	 * @see 	#openConnection();
	 * @since	1.0
	 */
	public void closeConnection()
	{
		if( !open )
		{
			return;
		}
		
		if( serialPort != null )
		{
			try
			{
				out.close();
				in.close();
			}
			catch( IOException e )
			{
				System.err.println( e );
			}
			
			serialPort.close();
			messageQueue.clear();
		}
		
		open = false;
	}
	
	/**
	 * Listens for data being sent to the com port.
	 * 
	 * @param	event	The event that was triggered, to be checked
	 * @since 	1.0
	 */
	public void serialEvent( SerialPortEvent event )
	{
		int data;
		byte[] buffer = new byte[1024];
		
		switch( event.getEventType() )
		{
		case SerialPortEvent.DATA_AVAILABLE:
			try
			{
				int len = 0;
				Date date = new Date();
				while( (data = in.read()) > -1 )
				{
					if( data == '\n' && len > 0 )
					{
						String message = new String( buffer, 0, len );
						message = message.trim();
						if( message.matches("X=\\d{3}\\.\\dY=\\d{3}\\.\\dR=(-)?\\d{3}(B=\\d\\.\\d{3})?") )
						{
							if( isDebug() )
							{
								System.out.println( portName + " " + date + " " + message );
							}
							messageQueue.add( date, message );
						}
						
						len = 0;
						date = new Date();
					}
					else
					{
						buffer[len++] = (byte)data;
					}
				}
			}
			catch( IOException e) {}
			break;
		}
	}
	
	/**
	 * Required for the runnable implementation to turn class into a thread
	 */
	public void run()
	{
		try
		{
			Thread.sleep( 20000 );
		}
		catch( InterruptedException e ) {}
	}
	
	/**
	 * Checks to see if the port is open or not for this instance.
	 * @return		true if open, false if closed
	 */
	public boolean isOpen()
	{
		return open;
	}
	
	/**
	 * Sets the port name string that the instance is supposed to connect to
	 * @param portName	the name of the port to connect to
	 * @see 	#openConnection 	This needs to be called after setting port name to connect.
	 */
	public void setPortName( String portName )
	{
		this.portName = portName;
	}
	
	/**
	 * Sends the string of data to the com port as a message
	 * @param message		the data to be sent
	 * @throws IOException	If something is wrong it will throw an error
	 */
	public void sendString( String message ) throws IOException
	{
		if( (message != null) && (message.length() > 0) && (isOpen()) )
		{
			char[] chars = new char[80];
			message.getChars(0, message.length(), chars, 0 );
			
			if( isDebug() )
			{
				System.out.println( "SerialConnection.sendString=" + message );
			}
			
			out.write( message.getBytes() );
			out.flush();
		}
	}
	
	/**
	 * Sets the debug setting to the value given as a parameter
	 * @param debug		sets the debug setting to this value
	 */
	public void setDebug( boolean debug )
	{
		this.debug = debug;
	}
	
	/**
	 * Returns whether debug mode is on or not
	 * @return		true if debugging is enabled, false if not.
	 */
	public boolean isDebug()
	{
		return this.debug;
	}
	
	/**
	 * Gets all available serial ports on the computer running this software
	 * and returns it as an array of strings.
	 * @return		an array of strings that contain all available port names
	 */
	public static String[] getAvailableSerialPorts()
	{
		Enumeration retPorts = CommPortIdentifier.getPortIdentifiers();
		String[] portNames = null;
		ArrayList<String> portList = new ArrayList<String>();
		
		while( retPorts.hasMoreElements() )
		{
			CommPortIdentifier comId = (CommPortIdentifier)retPorts.nextElement();
			if( comId.getPortType() == CommPortIdentifier.PORT_SERIAL )
			{
				portList.add( comId.getName() );
			}
		}
		
		Object[] ports = portList.toArray();
		
		if( portList != null )
		{
			portNames = new String[ports.length];
			
			for( int i = 0; i < ports.length; i++ )
			{
				portNames[i] = (String)ports[i];
			}
		}
		
		return portNames;
	}
	
	public void clearMessage()
	{
		messageQueue.clear();
	}
	
	public Object[] getMessage()
	{
		return messageQueue.getMessage();
	}
	
	public boolean isEmpty()
	{
		return messageQueue.isEmpty();
	}
	
	/**
	 * A class that contains a linked synchronized linked list of objects.
	 * Each object contains a date object and a string object.
	 * The date object is a time stamp for when the string was received from the
	 * com port.  The string object is the data that was received from the com port
	 * with all whitespace removed from the beginning and end.
	 * 
	 * @author Scott Norman <snorman1@umbc.edu>
	 *
	 */
	class MessageQueue
	{
		private LinkedList<Object[]> queue = new LinkedList<Object[]>();
		
		public void add( Date date, String message )
		{
			Object[] info = new Object[2];
			info[0] = date;
			info[1] = message;
			synchronized( queue )
			{
				queue.addLast( info );
			}
		}
		
		public void clear()
		{
			synchronized( queue )
			{
				queue.clear();
			}
		}
		
		public int size()
		{
			synchronized( queue )
			{
				return queue.size();
			}
		}
		
		public boolean isEmpty()
		{
			synchronized( queue )
			{
				return queue.isEmpty();
			}
		}
		
		public Object[] getMessage()
		{
			boolean empty = true;
			Object[] info = null;
			
			synchronized( queue )
			{
				empty = queue.isEmpty();
			}
			
			if( !empty )
			{
				synchronized( queue )
				{
					Object[] item = (Object[])queue.removeFirst();
					if( item != null )
					{
						info = item;
					}
				}
			}
			
			return info;
		}
	}
}
